package jmine.tec.security.josso.gateway.auth;

import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.security.auth.Subject;

import jmine.tec.security.josso.gateway.store.BasicCredentialStore;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.josso.auth.Credential;
import org.josso.auth.exceptions.SSOAuthenticationException;
import org.josso.auth.scheme.AuthenticationScheme;
import org.josso.auth.scheme.UsernamePasswordAuthScheme;
import org.josso.gateway.identity.service.BaseRole;
import org.josso.gateway.identity.service.BaseRoleImpl;
import org.josso.gateway.identity.service.BaseUserImpl;

import bancosys.tec.security.SecurityException;
import bancosys.tec.security.authorization.Permission;
import bancosys.tec.security.impl.web.WebSecurityContext;
import bancosys.tec.security.impl.web.WebSecurityManager;

/**
 * Implementacao de {@link AuthenticationScheme} que delega a autenticacao para um {@link WebSecurityManager}
 * 
 * @author takeshi
 */
public class SecurityManagerAuthScheme extends UsernamePasswordAuthScheme implements AuthenticationScheme {

    private static final Log LOG = LogFactory.getLog(SecurityManagerAuthScheme.class);

    public static final String SCHEME_NAME = "security-manager-authentication";

    public static final String WEB_SECURITY_CONTEXT_CREDENTIAL_NAME = "jmine.tec.security.web.context";

    private WebSecurityManager securityManager;

    /**
     * Default Constructor
     */
    public SecurityManagerAuthScheme() {
        this.setName(SCHEME_NAME);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean authenticate() throws SSOAuthenticationException {
        this.setAuthenticated(false);
        Credential[] knownCredentials = this._inputCredentials;
        String username = this.getUsername(knownCredentials);
        String password = this.getPassword(knownCredentials);
        WebSecurityContext securityContext = this.getWebSecurityContext(knownCredentials);
        Subject subj = this.login(username, password, securityContext);
        if (subj == null) {
            return false;
        }
        this._subject.getPublicCredentials().addAll(subj.getPublicCredentials());
        this._subject.getPrivateCredentials().addAll(subj.getPrivateCredentials());
        this.setAuthenticated(true);
        BasicCredentialStore cred = ((BasicCredentialStore) this.getCredentialStore());
        cred.put(new BaseUserImpl(username), this.toRoles(subj.getPublicCredentials()));
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Credential newCredential(String name, Object value) {
        if (WEB_SECURITY_CONTEXT_CREDENTIAL_NAME.equals(name)) {
            return new WebSecurityContextCredential(value);
        } else {
            return super.newCredential(name, value);
        }
    }

    /**
     * @param username username
     * @param password password
     * @param context context
     * @return Subject Subject
     */
    private Subject login(String username, String password, WebSecurityContext context) {
        try {
            return this.securityManager.login(username, password, context);
        } catch (SecurityException e) {
            LOG.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * Transforma um conjunto de instancias de {@link Permission} em {@link BaseRole}
     * 
     * @param publicCredentials Set
     * @return array de {@link BaseRole}
     */
    private BaseRole[] toRoles(Set<?> publicCredentials) {
        List<BaseRole> roles = new LinkedList<BaseRole>();
        for (Object object : publicCredentials) {
            if (object instanceof Permission) {
                Permission perm = (Permission) object;
                roles.add(new BaseRoleImpl(perm.getPermissionId()));
            }
        }
        return roles.toArray(new BaseRole[roles.size()]);
    }

    /**
     * @return the securityManager
     */
    public WebSecurityManager getSecurityManager() {
        return this.securityManager;
    }

    /**
     * @param securityManager the securityManager to set
     */
    public void setSecurityManager(WebSecurityManager securityManager) {
        this.securityManager = securityManager;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object clone() {
        SecurityManagerAuthScheme scheme = (SecurityManagerAuthScheme) super.clone();
        scheme.setSecurityManager(this.securityManager);
        return scheme;
    }

    /**
     * Busca o contexto de segurança web presente nas credenciais
     * 
     * @param credentials credentials
     * @return WebSecurityContext
     */
    protected WebSecurityContext getWebSecurityContext(Credential[] credentials) {
        WebSecurityContextCredential p = this.getWebSecurityContextCredential(credentials);
        if (p == null) {
            return null;
        }
        return (WebSecurityContext) p.getValue();
    }

    /**
     * Busca a credencial de contexto de segurança web
     * 
     * @param credentials credentials
     * @return credencial
     */
    protected WebSecurityContextCredential getWebSecurityContextCredential(Credential[] credentials) {
        for (int i = 0; i < credentials.length; i++) {
            if (credentials[i] instanceof WebSecurityContextCredential) {
                return (WebSecurityContextCredential) credentials[i];
            }
        }
        return null;
    }

}
